#pragma once
#include "gtest/gtest.h"
#include "bp/bp.h"
#include <vector>
using namespace std;

struct unicodetable {
	unicodetable(bp_char32_t code, const string &u8, const wstring &u16)
	{
		this->codepoint = code;
		this->bigEndianCodepoint = _BP_DWORD_LE_TO_BE(code);

		this->u8 = u8;
		u8char = 0;
		for (size_t i = 0; i < u8.size(); ++i)
		{
			u8char |= ((u8.at(i) & 0xff) << (i * 8));
		}

		this->u16 = u16;
		u16char = 0;
		u16bechar = 0;
		for (size_t i = 0; i < u16.size(); ++i)
		{
			bp_char16_t ch = u16.at(i);
			u16char |= ((ch & 0xffff) << (i * 16));

			bp_char16_t bech = _BP_WORD_LE_TO_BE(ch);
			u16be.append(1, bech);
			u16bechar |= ((bech & 0xffff) << (i * 16));
		}
	}

	bp_char32_t codepoint;
	bp_char32_t bigEndianCodepoint;
	string u8;
	bp_char32_t u8char;
	wstring u16;
	bp_char32_t u16char;
	wstring u16be;
	bp_char32_t u16bechar;
};

static string getUTF8Bom()
{
	return "\xEF\xBB\xBF";
}

static string unicodeTablesToUTF8String(const vector<unicodetable> &tables, bool withbom)
{
	string str;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		str.append(iter->u8);
	}

	if (withbom)
	{
		str = getUTF8Bom() + str;
	}

	return str;
}

static wstring getUTF16Bom(bool littleEndian)
{
	return littleEndian ? L"\xFEFF" : L"\xFFFE";
}

static wstring unicodeTablesToUTF16String(const vector<unicodetable> &tables, bool littleEndian, bool withbom)
{
	wstring str;
	for (auto iter = tables.begin(); iter != tables.end(); ++iter)
	{
		if (littleEndian) {
			str.append(iter->u16);
		}
		else {
			str.append(iter->u16be);
		}
	}

	if (withbom)
	{
		str = getUTF16Bom(littleEndian) + str;
	}

	return str;
}

static vector<unicodetable> createTestUnicodeTable()
{
	vector<unicodetable> tables;
	//1byte
	tables.push_back(unicodetable('A', "\x41", L"\x0041"));
	tables.push_back(unicodetable(0x7F, "\x7f", L"\x007F"));
	//2bytes
	tables.push_back(unicodetable(0x80, "\xc2\x80", L"\x0080"));
	tables.push_back(unicodetable(0x7F7, "\xdf\xb7", L"\x07F7"));
	tables.push_back(unicodetable(0x7FF, "\xdf\xbf", L"\x07FF"));
	////3bytes
	tables.push_back(unicodetable(0x800, "\xe0\xa0\x80", L"\x0800"));
	tables.push_back(unicodetable(0x906, "\xe0\xa4\x86", L"\x0906"));
	tables.push_back(unicodetable(0xFFFF, "\xef\xbf\xbf", L"\xFFFF"));
	////4bytes
	tables.push_back(unicodetable(0x10000, "\xf0\x90\x80\x80", L"\xD800\xDC00"));
	tables.push_back(unicodetable(0x2FA1C, "\xf0\xaf\xa8\x9c", L"\xD87E\xDE1C"));
	tables.push_back(unicodetable(0x1003FF, "\xf4\x80\x8f\xbf", L"\xDBC0\xDFFF"));
	tables.push_back(unicodetable(0x10FFFF, "\xf4\x8f\xbf\xbf", L"\xDBFF\xDFFF"));

	tables.push_back(unicodetable(0x800, "\xe0\xa0\x80", L"\x0800"));
	tables.push_back(unicodetable(0x80, "\xc2\x80", L"\x0080"));
	tables.push_back(unicodetable('A', "\x41", L"\x0041"));

	return tables;
}

static vector<string> createInvalidUTF8Strings()
{
	vector<string> vec;
	vec.push_back("\x80");
	//b110xxxxx, b11xxxxxx
	vec.push_back("\xC0\xC0");
	//b1110xxxx, b10xxxxxx, b11xxxxxx
	vec.push_back("\xE0\x80\xC0");
	//b11110xxx, b10xxxxxx, b10xxxxxx, b11xxxxxx
	vec.push_back("\xF0\x80\x80\xC0");
	return vec;
}

struct utf16_test_string {
	static vector<utf16_test_string> unicodeTablesToTestUTF16StringList(const vector<unicodetable> &tables)
	{
		vector<utf16_test_string> vec;
		vec.push_back(utf16_test_string(tables, true, true));
		vec.push_back(utf16_test_string(tables, true, false));
		vec.push_back(utf16_test_string(tables, false, true));
		vec.push_back(utf16_test_string(tables, false, false));
		return vec;
	}

	wstring str;
	bp_byte_order byte_order;
	bool withbom;

	utf16_test_string(const vector<unicodetable> &tables, bool le, bool withbom)
	{
		this->str = unicodeTablesToUTF16String(tables, le, withbom);
		this->byte_order = le ? bp_little_endian : bp_big_endian;
		this->withbom = withbom;
	}

	const bp_char16_t * ptr() const
	{
		return (const bp_char16_t *)str.c_str();
	}

	size_t size() const
	{
		return str.size();
	}

	size_t bomHeaderSize() const
	{
		return withbom ? getUTF16Bom(byte_order == bp_little_endian).size() : 0;
	}
};

struct utf8_test_string
{
	string str;
	bool withbom;

	utf8_test_string(const vector<unicodetable> &tables, bool withbom)
	{
		this->str = unicodeTablesToUTF8String(tables, withbom);
		this->withbom = withbom;
	}

	const bp_char8_t *ptr() const
	{
		return (const bp_char8_t *)str.c_str();
	}

	size_t size() const
	{
		return str.size();
	}
};

static vector<utf8_test_string> unicodeTablesToTestUTF8StringList(const vector<unicodetable> &tables)
{
	vector<utf8_test_string> vec;
	vec.push_back(utf8_test_string(tables, false));
	vec.push_back(utf8_test_string(tables, true));
	return vec;
}

struct bytes_input {
	bytes_input(const bp_byte_t *bytes, size_t size, bp_string_encode charset)
		: bytes(bytes, bytes + size), charset(charset) {
	}

	const bp_byte_t *getBytes() const {
		return (const bp_byte_t *)bytes.data();
	}

	size_t getSize() const {
		return bytes.size();
	}

	string bytes;
	bp_string_encode charset;
};